#include "httpresponse.h"

using namespace std;

// 定义了常见的文件扩展名及其对应的MIME类型，用于确定HTTP响应的Content-Type头部
const unordered_map<string, string> HttpResponse::SUFFIX_TYPE = 
{
    { ".html",  "text/html" },
    { ".xml",   "text/xml" },
    { ".xhtml", "application/xhtml+xml" },
    { ".txt",   "text/plain" },
    { ".rtf",   "application/rtf" },
    { ".pdf",   "application/pdf" },
    { ".word",  "application/nsword" },
    { ".png",   "image/png" },
    { ".gif",   "image/gif" },
    { ".jpg",   "image/jpeg" },
    { ".jpeg",  "image/jpeg" },
    { ".au",    "audio/basic" },
    { ".mpeg",  "video/mpeg" },
    { ".mpg",   "video/mpeg" },
    { ".avi",   "video/x-msvideo" },
    { ".gz",    "application/x-gzip" },
    { ".tar",   "application/x-tar" },
    { ".css",   "text/css "},
    { ".js",    "text/javascript "},
};

// 存储HTTP状态码与其相应的短语描述，如200对应“OK”，404对应“Not Found”等
const unordered_map<int, string> HttpResponse::CODE_STATUS = 
{
    { 200, "OK"},
    { 400, "Bad Request"},
    { 403, "Forbidden"},
    { 404, "Not Found"},
};

// 存储错误状态码对应的服务端本地 错误页面路径
const unordered_map<int, string> HttpResponse::CODE_PATH
{
    { 400, "/400.html"},
    { 403, "/403.html"},
    { 404, "/404.html"},
};

// 构造函数初始化所有成员变量，确保对象开始处于已知状态
HttpResponse::HttpResponse() {
    code_ = -1;
    path_ = srcDir_ = "";
    isKeepAlive_ = false;
    mmFile_ = nullptr;
    mmFileStat_ = { 0 };
};

// 析构函数负责清理工作，特别是解除内存映射，防止内存泄漏
HttpResponse::~HttpResponse() {
    UnmapFile();
}

// 初始化HTTP响应对象，设置状态码、是否保持连接、资源路径等
void HttpResponse::Init(const string& srcDir, string& path, bool isKeepAlive, int code) {
    assert(srcDir != "");
    if(mmFile_) {UnmapFile();}
    code_ = code;  // 设置状态码
    isKeepAlive_ = isKeepAlive;
    path_ = path;
    srcDir_ = srcDir;
    mmFile_ = nullptr;
    mmFileStat_ = { 0 };
}

// 构建完整的HTTP响应，包括检查文件状态、设置状态码、生成错误HTML页面（如有必要），然后依次添加状态行、报头和内容至缓冲区
void HttpResponse::MakeResponse(Buffer& buff) {
    // 判断请求的资源文件
    if(stat((srcDir_ + path_).data(), &mmFileStat_) < 0 || S_ISDIR(mmFileStat_.st_mode)) {
        code_ = 404;
    } else if(!(mmFileStat_.st_mode & S_IROTH)) {
        code_ = 403;
    } else if(code_ == -1) {
        code_ = 200;
    }
    ErrorHtml_();
    AddStateLine_(buff);        // 添加状态行
    AddHeader_(buff);           // 添加消息报头
    AddContent_(buff);          // 添加响应正文,会用到内存映射
}

// File 和 FileLen：提供对内存映射文件的访问，包括获取文件指针和文件长度
char* HttpResponse::File() {
    return mmFile_;
}

size_t HttpResponse::FileLen() const {
    return mmFileStat_.st_size;
}

// 如果遇到错误状态码，则查找并替换为指定的错误页面路径
void HttpResponse::ErrorHtml_() {
    if(CODE_PATH.count(code_) == 1) {
        path_ = CODE_PATH.find(code_)->second;
        stat((srcDir_ + path_).data(), &mmFileStat_);
    }
}

// 添加HTTP状态行到缓冲区
void HttpResponse::AddStateLine_(Buffer& buff) {
    string status;
    if(CODE_STATUS.count(code_) == 1) {
        status = CODE_STATUS.find(code_)->second;  //设置状态码
    } else {
        code_ = 400;
        status = CODE_STATUS.find(code_)->second;
    }
    // 将响应状态行添加进buffer
    buff.Append("HTTP/1.1 " + to_string(code_) + " " + status + "\r\n");
}

// 添加HTTP头部信息，包括 Connection 字段 和 Content-Type 字段
void HttpResponse::AddHeader_(Buffer& buff) {
    buff.Append("Connection: ");
    if(isKeepAlive_) {
        buff.Append("keep-alive\r\n");
        buff.Append("keep-alive: max=6, timeout=120\r\n");
    } else {
        buff.Append("close\r\n");
    }
    buff.Append("Content-type: " + GetFileType_() + "\r\n");
}

// 进行文件的内存映射，并添加 Content-Length字段 和 实际文件内容 到缓冲区
void HttpResponse::AddContent_(Buffer& buff) {
    // 打开请求页面所对应的资源文件
    int srcFd = open((srcDir_ + path_).data(), O_RDONLY); // 只读文件
    if(srcFd < 0) {
        ErrorContent(buff, "File NotFound!");
        return;
    }

    /*
        将文件映射到内存 提高文件的访问速度
        MAP_PRIVATE 建立一个写入时拷贝的私有映射
    */
    LOG_DEBUG("file path %s", (srcDir_ + path_).data());
    // 进行内存映射,返回内存地址
    int* mmRet = (int*)mmap(0, mmFileStat_.st_size, PROT_READ, MAP_PRIVATE, srcFd, 0);
    if(*mmRet == -1) {
        ErrorContent(buff, "File NotFound!");
        return;
    }
    mmFile_ = (char*)mmRet;
    close(srcFd);   // 关闭映射的文件
    // 消息报头之后的回车换行
    buff.Append("Content-length: " + to_string(mmFileStat_.st_size) + "\r\n\r\n");
}

// 解除内存映射
void HttpResponse::UnmapFile() {
    if(mmFile_) {
        munmap(mmFile_, mmFileStat_.st_size); //释放内存映射
        mmFile_ = nullptr;
    }
}

// 根据文件路径获得其MIME类型
string HttpResponse::GetFileType_() {
    string::size_type idx = path_.find_last_of('.');    // 找最后一次出现的位置
    // 失败返回npos
    if(idx == string::npos) {
        return "text/plain";
    }
    string suffix = path_.substr(idx);  // 找后的所有字符
    if(SUFFIX_TYPE.count(suffix) == 1) {
        return SUFFIX_TYPE.find(suffix)->second; // 返回该类型对应的文件路径
    }
    return "text/plain";
}

// 当发生错误时，构建并添加包含错误消息的HTML内容作为响应主体
void HttpResponse::ErrorContent(Buffer& buff, string message) {
    string body;
    string status;
    body += "<html><title>Error</title>";
    body += "<body bgcolor=\"ffffff\">";
    if(CODE_STATUS.count(code_) == 1) {
        status = CODE_STATUS.find(code_)->second;
    } else {
        status = "Bad Request";
    }
    body += to_string(code_) + " : " + status + "\n"; //
    body += "<p>" + message + "</p>";
    body += "<hr><em>Yang's TinyWebServer</em></body></html>";

    buff.Append("Content-length: " + to_string(body.size()) + "\r\n\r\n");
    buff.Append(body);  // 添加响应主体
}